Introduction
IMPORTANT NOTE: From Hibernate 4.0.1 onwards, Infinispan now interacts as a synchronization rather than as an XA resource with the transaction manager when used as second-level cache, so there's no longer need to apply any of the changes suggested below!
Infinispans predecessor JBossCache requires integration with JTA when used as 2L-cache for a Hibernate application. At the moment of writing this article (Hibernate 3.5.0.Beta3) also Infinspan requires integration with JTA. Hibernate integrated with JTA is already largely used in EJB applications servers, but most users using Hibernate with Java SE outside any EJB container, still use the plain JDBC approach instead to use JTA.
According Hiberante documentation it should also possible to integrate JTA in a standalone application outside any EJB container, but I did hardly find any documentation how to do that in detail. (probably the reason is, that probably 95% of people is using hibernate within a EJB app. server or using SPRING). This article should give you some example how to realize a standalone Hibernate app. outside of a EJB container with JTA integration (and using Infinispan 2nd level cache).
As first thing you have to choose which implementation of TransactionManager to take. This article comes with examples for following OpenSource TransactionManagers:
-
JBoss
-
JOTM
-
Bitronix
-
Atomikos
Datasource/Transaction interaction
A very important aspect is not forgetting to couple the datasource with your transaction manager. In other words, the corresponding XAResource must be onto the transaction manager, otherwise only DML-statements but no commits/rollbacks are propagated to your database.
JBoss Transactions
The example with JBoss Transactions Transaction Manager was the most complex to implement, as JBoss's TransactionManager and UserTransaction objects are not declared serializable whilst it's JNDI-server isn't able to bind non serializable objects out of the box. Special use of NonSerializableFactory is needed, requiring some additional custom code:
import hello.A; // a persistent class
import java.io.Serializable;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.NameNotFoundException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.persistence.EntityManager;
import javax.persistence.Persistence;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
import org.enhydra.jdbc.standard.StandardXADataSource;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.ejb.HibernateEntityManagerFactory;
import org.hibernate.transaction.JBossTransactionManagerLookup;
import org.infinispan.transaction.lookup.JBossStandaloneJTAManagerLookup;
import org.jboss.util.naming.NonSerializableFactory;
import org.jnp.interfaces.NamingContext;
import org.jnp.server.Main;
import org.jnp.server.NamingServer;
public class JTAStandaloneExampleJBossTM {
static JBossStandaloneJTAManagerLookup _ml = new JBossStandaloneJTAManagerLookup();
public static void main(String[] args) {
try {
// Create an in-memory jndi
NamingServer namingServer = new NamingServer();
NamingContext.setLocal(namingServer);
Main namingMain = new Main();
namingMain.setInstallGlobalService(true);
namingMain.setPort(-1);
namingMain.start();
Properties props = new Properties();
props.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
props.put("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces");
InitialContext ictx = new InitialContext( props );
// as JBossTransactionManagerLookup extends JNDITransactionManagerLookup we must also register the TransactionManager
bind("java:/TransactionManager", _ml.getTransactionManager(), _ml.getTransactionManager().getClass(), ictx);
// also the UserTransaction must be registered on jndi: org.hibernate.transaction.JTATransactionFactory#getUserTransaction() requires this
bind(new JBossTransactionManagerLookup().getUserTransactionName(),_ml.getUserTransaction(),_ml.getUserTransaction().getClass(), ictx);
ExtendedXADataSource xads = new ExtendedXADataSource();
xads.setDriverName("org.hsqldb.jdbcDriver");
xads.setDriverName("com.p6spy.engine.spy.P6SpyDriver"); // comment this line if you don't want p6spy-logging
xads.setUrl("jdbc:hsqldb:hsql://localhost");
//xads.setTransactionManager(_ml.getTransactionManager()); useless here as this information is not serialized
ictx.bind("java:/MyDatasource", xads);
final HibernateEntityManagerFactory emf = (HibernateEntityManagerFactory) Persistence.createEntityManagerFactory("helloworld");
UserTransaction userTransaction = _ml.getUserTransaction();
userTransaction.setTransactionTimeout(300000);
//SessionFactory sf = (SessionFactory) ictx.lookup("java:/hibernate/MySessionFactory"); // if hibernate.session_factory_name set
final SessionFactory sf = emf.getSessionFactory();
userTransaction.begin();
EntityManager em = emf.createEntityManager();
// do here your persistent work
A a = new A();
a.name= "firstvalue";
em.persist(a);
em.flush(); // do manually flush here as apparently FLUSH_BEFORE_COMPLETION seems not work, bug ?
System.out.println("\nCreated and flushed instance a with id : " + a.oid + " a.name set to:" + a.name);
System.out.println("Calling userTransaction.commit() (Please check if the commit is effectively executed!)");
userTransaction.commit();
ictx.close();
namingMain.stop();
emf.close();
} catch (Exception e) {
e.printStackTrace();
}
System.exit(0);
}
public static class ExtendedXADataSource extends StandardXADataSource { // XAPOOL
@Override
public Connection getConnection() throws SQLException {
if (getTransactionManager() == null) { // although already set before, it results null again after retrieving the datasource by jndi
TransactionManager tm; // this is because the TransactionManager information is not serialized.
try {
tm = _ml.getTransactionManager();
} catch (Exception e) {
throw new SQLException(e);
}
setTransactionManager(tm); // resets the TransactionManager on the datasource retrieved by jndi,
// this makes the datasource JTA-aware
}
// According to Enhydra documentation, here we must return the connection of our XAConnection
// see http://cvs.forge.objectweb.org/cgi-bin/viewcvs.cgi/xapool/xapool/examples/xapooldatasource/DatabaseHelper.java?sortby=rev
return super.getXAConnection().getConnection();
}
}
/**
* Helper method that binds the a non serializable object to the JNDI tree.
*
* @param jndiName Name under which the object must be bound
* @param who Object to bind in JNDI
* @param classType Class type under which should appear the bound object
* @param ctx Naming context under which we bind the object
* @throws Exception Thrown if a naming exception occurs during binding
*/
private static void bind(String jndiName, Object who, Class classType, Context ctx) throws Exception {
// Ah ! This service isn't serializable, so we use a helper class
NonSerializableFactory.bind(jndiName, who);
Name n = ctx.getNameParser("").parse(jndiName);
while (n.size() > 1) {
String ctxName = n.get(0);
try {
ctx = (Context) ctx.lookup(ctxName);
} catch (NameNotFoundException e) {
System.out.println("Creating subcontext:" + ctxName);
ctx = ctx.createSubcontext(ctxName);
}
n = n.getSuffix(1);
}
// The helper class NonSerializableFactory uses address type nns, we go on to
// use the helper class to bind the service object in JNDI
StringRefAddr addr = new StringRefAddr("nns", jndiName);
Reference ref = new Reference(classType.getName(), addr, NonSerializableFactory.class.getName(), null);
ctx.rebind(n.get(0), ref);
}
private static void unbind(String jndiName, Context ctx) throws Exception {
NonSerializableFactory.unbind(jndiName);
ctx.unbind(jndiName);
}
}
The content of the corresponding complete persistence.xml:
<persistence xmlns="http://java.sun.com/xml/ns/persistence" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance" xsi:schemaLocation="http://java.sun.com/xml/ns/persistence http://java.sun.com/xml/ns/persistence/persistence_1_0.xsd" version="1.0">
<persistence-unit name="helloworld" transaction-type="JTA">
<jta-data-source>java:/MyDatasource</jta-data-source>
<properties>
<property name="hibernate.hbm2ddl.auto" value = "create"/>
<property name="hibernate.archive.autodetection" value="class, hbm"/>
<property name="hibernate.dialect" value="org.hibernate.dialect.HSQLDialect"/>
<property name="hibernate.jndi.class" value="org.jnp.interfaces.NamingContextFactory"/>
<property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.JBossTransactionManagerLookup"/>
<property name="current_session_context_class" value="jta"/>
<!-- <property name="hibernate.session_factory_name" value="java:/hibernate/MySessionFactory"/> optional -->
<property name="hibernate.transaction.factory_class" value="org.hibernate.transaction.JTATransactionFactory"/>
<property name="hibernate.connection.release_mode" value="auto"/>
<!-- setting above is important using XA-DataSource on SQLServer,
otherwise SQLServerException: The function START: has failed. No transaction cookie was returned.-->
<property name="hibernate.cache.use_second_level_cache" value="true"/>
<property name="hibernate.cache.use_query_cache" value="true"/>
<property name="hibernate.cache.region.factory_class" value="org.hibernate.cache.infinispan.InfinispanRegionFactory"/>
</properties>
</persistence-unit>
</persistence>
JOTM
The example with JOTM is more simple, but apparently it's JNDI implementation is not useable without wasting any rmi port. So it is not completely 'standalone' as the JNDI service is exposed outside your virtual machine.
import hello.A; // a persistent class
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.persistence.EntityManager;
import javax.persistence.EntityManagerFactory;
import javax.persistence.Persistence;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
import org.enhydra.jdbc.standard.StandardXADataSource;
import org.hibernate.transaction.JOTMTransactionManagerLookup;
import org.objectweb.jotm.Jotm;
import org.objectweb.transaction.jta.TMService;
public class JTAExampleJOTM {
static JOTMTransactionManagerLookup _ml = new JOTMTransactionManagerLookup();
public static class ExtendedXADataSource extends StandardXADataSource { // XAPOOL
@Override
public Connection getConnection() throws SQLException {
if (getTransactionManager() == null) { // although already set before, it results null again after retrieving the datasource by jndi
TransactionManager tm; // this is because the TransactionManager information is not serialized.
try {
tm = _ml.getTransactionManager(null);
} catch (Exception e) {
throw new SQLException(e);
}
setTransactionManager(tm); // resets the TransactionManager on the datasource retrieved by jndi,
// this makes the datasource JTA-aware
}
// According to Enhydra documantation, here we must return the connection of our XAConnection
// see http://cvs.forge.objectweb.org/cgi-bin/viewcvs.cgi/xapool/xapool/examples/xapooldatasource/DatabaseHelper.java?sortby=rev
return super.getXAConnection().getConnection();
}
}
public static void main( String[] args )
{
try
{
java.rmi.registry.LocateRegistry.createRegistry(1099); // also possible to lauch by command line rmiregistry
System.out.println("RMI registry ready.");
// following properties can be left out if specifying thes values in a file jndi.properties located into classpath
Properties props = new Properties();
props.put(Context.INITIAL_CONTEXT_FACTORY, "org.ow2.carol.jndi.spi.MultiOrbInitialContextFactory");
InitialContext jndiCtx = new InitialContext(props);
// XAPOOL
ExtendedXADataSource xads = new ExtendedXADataSource();
xads.setDriverName("org.hsqldb.jdbcDriver");
xads.setDriverName("com.p6spy.engine.spy.P6SpyDriver");
xads.setUrl("jdbc:hsqldb:hsql://localhost");
jndiCtx.bind("java:/MyDatasource", xads);
/* startup JOTM */
TMService jotm = new Jotm(true, false);
jotm.getUserTransaction().setTransactionTimeout(36000); // secs, important JOTM default is only 60 secs !
/* and get a UserTransaction */
UserTransaction userTransaction = jotm.getUserTransaction();
jndiCtx.bind("java:comp/UserTransaction", jotm.getUserTransaction()); // this is needed by hibernates JTATransactionFactory
/* get the Hibernate SessionFactory */
EntityManagerFactory emf = Persistence.createEntityManagerFactory("helloworld");
//SessionFactory sf = (SessionFactory) jndiCtx.lookup("java:/hibernate/MySessionFactory");
// begin a new Transaction
userTransaction.begin();
EntityManager em = emf.createEntityManager();
A a = new A();
a.name= "firstvalue";
em.persist(a);
em.flush(); // do manually flush here as apparently FLUSH_BEFORE_COMPLETION seems not work, bug ?
System.out.println("Calling userTransaction.commit() (Please check if the commit is effectively executed!)");
userTransaction.commit();
// stop the transaction manager
jotm.stop();
jndiCtx.close();
emf.close();
}
catch( Exception e )
{
e.printStackTrace();
}
System.exit(0);
}
}
Adjust following 2 properties in your persistence.xml:
<property name="hibernate.jndi.class" value="org.ow2.carol.jndi.spi.MultiOrbInitialContextFactory"/>
<property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.JOTMTransactionManagerLookup"/>
For using the JTA Hibernate application as servlet in tomcat please read http://jotm.objectweb.org/current/jotm/doc/howto-tomcat-jotm.html and also https://forum.hibernate.org/viewtopic.php?f=1&t=1003866
Bitronix
The Transaction Manager comes bundled with a fake in memory jndi-implementation which is ideal for standalone purpose. To integrate with Infinispan I did need a ad-hoc pre-alpha improvement (see attached btm-ispn.jar by courtesy of Mr. Ludivic Orban). BitronixTM offers the so-called Last Resource Commit optimization (aka Last Resource Gambit or Last Agent optimization) and it allows a single non-XA database to participate in a XA transaction by cleverly ordering the resources. "Last Resource Commit" is not part of the XA spec as it doesn't cover the transaction-recovery aspect, so if your database does not support XA (or if you don't wish to have the Xa-driver performance overhead against the plain jdbc) then the "Last Resource Commit" feature should be ideal for the combination 1 single database plus infinispan.
import hello.A; // a persistent class
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.persistence.EntityManager;
import javax.persistence.Persistence;
import javax.transaction.UserTransaction;
import org.hibernate.cache.infinispan.InfinispanRegionFactory;
import org.hibernate.ejb.HibernateEntityManagerFactory;
import org.hibernate.impl.SessionFactoryImpl;
import org.infinispan.manager.CacheManager;
import bitronix.tm.resource.ResourceRegistrar;
import bitronix.tm.resource.infinispan.InfinispanCacheManager;
import bitronix.tm.resource.jdbc.PoolingDataSource;
public class JTAExampleBTM {
public static void main(String[] args) {
try {
Properties props = new Properties();
props.put(Context.INITIAL_CONTEXT_FACTORY, "bitronix.tm.jndi.BitronixInitialContextFactory");
// Attention: BitronixInitialContextFactory is'nt a real jndi implementation: you can't do explicit bindings
// It is ideal for hiberante standalone usage, as it automatically 'binds' the needed things: datasource + usertransaction
System.out.println("create initial context");
InitialContext ictx = new InitialContext(props);
PoolingDataSource myDataSource = new PoolingDataSource();
myDataSource.setClassName("bitronix.tm.resource.jdbc.lrc.LrcXADataSource");
myDataSource.setMaxPoolSize(5);
myDataSource.setAllowLocalTransactions(true);
myDataSource.getDriverProperties().setProperty("driverClassName", "com.p6spy.engine.spy.P6SpyDriver");
myDataSource.getDriverProperties().setProperty("url", "jdbc:hsqldb:hsql://localhost");
myDataSource.getDriverProperties().setProperty("user", "sa");
myDataSource.getDriverProperties().setProperty("password", "");
myDataSource.setUniqueName("java:/MyDatasource");
myDataSource.setAutomaticEnlistingEnabled(true); // important to keep it to true (default), otherwise commits/rollbacks are not propagated
myDataSource.init(); // does also register the datasource on the Fake-JNDI with Unique Name
org.hibernate.transaction.BTMTransactionManagerLookup lokhiberante = new org.hibernate.transaction.BTMTransactionManagerLookup();
HibernateEntityManagerFactory emf = (HibernateEntityManagerFactory) Persistence.createEntityManagerFactory("helloworld");
SessionFactoryImpl sfi = (SessionFactoryImpl) emf.getSessionFactory();
InfinispanRegionFactory infinispanregionfactory = (InfinispanRegionFactory) sfi.getSettings().getRegionFactory();
CacheManager manager = infinispanregionfactory.getCacheManager();
// register Inifinispan as a BTM resource
InfinispanCacheManager icm = new InfinispanCacheManager();
icm.setUniqueName("infinispan");
ResourceRegistrar.register(icm);
icm.setManager(manager);
final UserTransaction userTransaction = (UserTransaction) ictx.lookup(lokhiberante.getUserTransactionName());
// begin a new Transaction
userTransaction.begin();
EntityManager em = emf.createEntityManager();
A a = new A();
a.name= "firstvalue";
em.persist(a);
em.flush(); // do manually flush here as apparently FLUSH_BEFORE_COMPLETION seems not work, bug ?
System.out.println("Calling userTransaction.commit() (Please check if the commit is effectively executed!)");
userTransaction.commit();
emf.close();
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
System.exit(0);
}
}
Adjust following 2 properties in your corresponding persistence.xml:
<property name="hibernate.jndi.class" value="bitronix.tm.jndi.BitronixInitialContextFactory"/>
<property name="hibernate.transaction.manager_lookup_class" value="org.hibernate.transaction.BTMTransactionManagerLookup"/>
Atominkos
Last but not least, the Atomikos Transaction manager. It is currently the unique Transaction manager I've found with a online-documentation on how to integrate with Hiberantewithout Spring, outside any J2EE container.. It seems to be the unique supporting XaDataSource together with Pooling, so it doesn't matter that It does not come with it's own JNDI implementation (we will use the one of JBoss in following example).
import hello.A; // a persistent class
import java.io.Serializable;
import java.sql.Connection;
import java.sql.SQLException;
import java.util.Properties;
import javax.naming.Context;
import javax.naming.InitialContext;
import javax.naming.Name;
import javax.naming.NameNotFoundException;
import javax.naming.Reference;
import javax.naming.StringRefAddr;
import javax.persistence.EntityManager;
import javax.persistence.Persistence;
import javax.transaction.TransactionManager;
import javax.transaction.UserTransaction;
import org.hibernate.Session;
import org.hibernate.SessionFactory;
import org.hibernate.ejb.HibernateEntityManagerFactory;
import org.hibernate.impl.SessionFactoryImpl;
import org.jboss.util.naming.NonSerializableFactory;
import org.jnp.interfaces.NamingContext;
import org.jnp.server.Main;
import org.jnp.server.NamingServer;
import com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup;
import com.atomikos.jdbc.AtomikosDataSourceBean;
import com.atomikos.jdbc.SimpleDataSourceBean;
public class JTAStandaloneExampleAtomikos {
public static void main(String[] args) {
try {
// Create an in-memory jndi
NamingServer namingServer = new NamingServer();
NamingContext.setLocal(namingServer);
Main namingMain = new Main();
namingMain.setInstallGlobalService(true);
namingMain.setPort(-1);
namingMain.start();
Properties props = new Properties();
props.put(Context.INITIAL_CONTEXT_FACTORY, "org.jnp.interfaces.NamingContextFactory");
props.put("java.naming.factory.url.pkgs", "org.jboss.naming:org.jnp.interfaces");
InitialContext ictx = new InitialContext( props );
AtomikosDataSourceBean ds = new AtomikosDataSourceBean();
ds.setUniqueResourceName("sqlserver_ds");
ds.setXaDataSourceClassName("com.microsoft.sqlserver.jdbc.SQLServerXADataSource");
Properties p = new Properties();
p.setProperty ( "user" , "sa" );
p.setProperty ( "password" , "" );
p.setProperty ( "serverName" , "myserver" );
ds.setXaProperties ( p );
ds.setPoolSize(5);
bind("java:/MyDatasource", ds, ds.getClass(), ictx);
TransactionManagerLookup _ml = new TransactionManagerLookup();
UserTransaction userTransaction = new com.atomikos.icatch.jta.UserTransactionImp();
bind("java:/TransactionManager", _ml.getTransactionManager(null), _ml.getTransactionManager(null).getClass(), ictx);
bind("java:comp/UserTransaction", userTransaction, userTransaction.getClass(), ictx);
HibernateEntityManagerFactory emf = (HibernateEntityManagerFactory) Persistence.createEntityManagerFactory("helloworld");
// begin a new Transaction
userTransaction.begin();
EntityManager em = emf.createEntityManager();
A a = new A();
a.name= "firstvalue";
em.persist(a);
em.flush(); // do manually flush here as apparently FLUSH_BEFORE_COMPLETION seems not work, bug ?
System.out.println("Calling userTransaction.commit() (Please check if the commit is effectively executed!)");
userTransaction.commit();
emf.close();
} catch (Exception e) {
e.printStackTrace();
System.exit(1);
}
System.exit(0);
}
/**
* Helper method that binds the a non serializable object to the JNDI tree.
*
* @param jndiName Name under which the object must be bound
* @param who Object to bind in JNDI
* @param classType Class type under which should appear the bound object
* @param ctx Naming context under which we bind the object
* @throws Exception Thrown if a naming exception occurs during binding
*/
private static void bind(String jndiName, Object who, Class<?> classType, Context ctx) throws Exception {
// Ah ! This service isn't serializable, so we use a helper class
NonSerializableFactory.bind(jndiName, who);
Name n = ctx.getNameParser("").parse(jndiName);
while (n.size() > 1) {
String ctxName = n.get(0);
try {
ctx = (Context) ctx.lookup(ctxName);
} catch (NameNotFoundException e) {
System.out.println("Creating subcontext:" + ctxName);
ctx = ctx.createSubcontext(ctxName);
}
n = n.getSuffix(1);
}
// The helper class NonSerializableFactory uses address type nns, we go on to
// use the helper class to bind the service object in JNDI
StringRefAddr addr = new StringRefAddr("nns", jndiName);
Reference ref = new Reference(classType.getName(), addr, NonSerializableFactory.class.getName(), null);
ctx.rebind(n.get(0), ref);
}
private static void unbind(String jndiName, Context ctx) throws Exception {
NonSerializableFactory.unbind(jndiName);
ctx.unbind(jndiName);
}
}
Adjust follwing 2 properties in your corresponding persistence.xml:
<property name="hibernate.jndi.class" value="org.jnp.interfaces.NamingContextFactory"/>
<property name="hibernate.transaction.manager_lookup_class" value="com.atomikos.icatch.jta.hibernate3.TransactionManagerLookup"/>
And create a file named jta.properties in your classpath with following content:
com.atomikos.icatch.service=com.atomikos.icatch.standalone.UserTransactionServiceFactory
com.atomikos.icatch.automatic_resource_registration=false
com.atomikos.icatch.console_log_level=WARN
com.atomikos.icatch.force_shutdown_on_vm_exit=true
com.atomikos.icatch.enable_logging=false